package com.qualityeclipse.genealogy.parts;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.FreeformLayer;
import org.eclipse.draw2d.FreeformLayout;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.MarginBorder;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editparts.AbstractGraphicalEditPart;
import org.eclipse.gef.editpolicies.RootComponentEditPolicy;
import org.eclipse.gef.editpolicies.XYLayoutEditPolicy;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.gef.requests.CreateRequest;

import com.qualityeclipse.genealogy.commands.CreateMarriageCommand;
import com.qualityeclipse.genealogy.commands.CreateNoteCommand;
import com.qualityeclipse.genealogy.commands.CreatePersonCommand;
import com.qualityeclipse.genealogy.commands.MoveAndResizeGenealogyElementCommand;
import com.qualityeclipse.genealogy.commands.ReparentNoteCommand;
import com.qualityeclipse.genealogy.model.GenealogyElement;
import com.qualityeclipse.genealogy.model.GenealogyGraph;
import com.qualityeclipse.genealogy.model.Marriage;
import com.qualityeclipse.genealogy.model.Note;
import com.qualityeclipse.genealogy.model.NoteContainer;
import com.qualityeclipse.genealogy.model.Person;
import com.qualityeclipse.genealogy.model.listener.GenealogyGraphListener;
import com.qualityeclipse.genealogy.policies.NonResizableMarriageEditPolicy;

/**
 * The {@link EditPart} for the {@link GenealogyGraph} model object. This EditPart is
 * responsible for creating the layer in which all other figures are placed and for
 * returning the collection of top level model objects to be displayed in that layer.
 */
public class GenealogyGraphEditPart extends AbstractGraphicalEditPart
    implements GenealogyGraphListener
{
    public GenealogyGraphEditPart(GenealogyGraph genealogyGraph) {
        setModel(genealogyGraph);
        genealogyGraph.addGenealogyGraphListener(this);
    }

    @Override
    public GenealogyGraph getModel() {
        return (GenealogyGraph) super.getModel();
    }

    /**
     * Construct and return the layer in which all other genealogy figures are displayed
     */
    @Override
    protected IFigure createFigure() {
        Figure figure = new FreeformLayer();
        figure.setBorder(new MarginBorder(3));
        figure.setLayoutManager(new FreeformLayout());
        return figure;
    }

    /**
     * Return a collection of top level genealogy model objects to be displayed
     */
    @Override
    protected List<GenealogyElement> getModelChildren() {
        List<GenealogyElement> allObjects = new ArrayList<GenealogyElement>();
        allObjects.addAll(getModel().getMarriages());
        allObjects.addAll(getModel().getPeople());
        allObjects.addAll(getModel().getNotes());
        return allObjects;
    }

    @Override
    protected void createEditPolicies() {

        // Disallows the removal of this edit part from its parent
        installEditPolicy(EditPolicy.COMPONENT_ROLE, new RootComponentEditPolicy());

        // Handles constraint changes (e.g. moving and/or resizing) of model elements
        // and creation of new model elements
        installEditPolicy(EditPolicy.LAYOUT_ROLE, new XYLayoutEditPolicy() {
            @Override
            protected Command getCreateCommand(CreateRequest request) {
                Object type = request.getNewObjectType();
                Rectangle box = (Rectangle) getConstraintFor(request);
                GenealogyGraph graph = getModel();
                if (type == Person.class) {
                    Person person = (Person) request.getNewObject();
                    return new CreatePersonCommand(graph, person, box);
                }
                if (type == Marriage.class) {
                    Marriage marriage = (Marriage) request.getNewObject();
                    return new CreateMarriageCommand(graph, marriage, box);
                }
                if (type == Note.class) {
                    Note note = (Note) request.getNewObject();
                    return new CreateNoteCommand(graph, note, box);
                }
                return null;
            }

            /**
             * IGNORE: This method is a holdover from earlier GEF frameworks
             * and will be removed in future versions of GEF.
             */
            @Override
            protected Command createChangeConstraintCommand(EditPart child, Object constraint) {
                return null;
            }

            /**
             * Return a command for moving elements around the canvas
             */
            @Override
            protected Command createChangeConstraintCommand(
                ChangeBoundsRequest request, EditPart child, Object constraint
            ) {
                GenealogyElement elem = (GenealogyElement) child.getModel();
                Rectangle box = (Rectangle) constraint;
                return new MoveAndResizeGenealogyElementCommand(elem, box);
            }

            /**
             * Exclude MarriageEditParts from being resized
             */
            @Override
            protected EditPolicy createChildEditPolicy(EditPart child) {
                if (child instanceof MarriageEditPart) {
                    return new NonResizableMarriageEditPolicy();
                }
                return super.createChildEditPolicy(child);
            }

            /**
             * Called when a Note is dragged from a Person on the canvas.
             * Return a command for reparenting the note
             */
            @Override
            protected Command createAddCommand(EditPart child, Object constraint) {
                NoteContainer oldContainer = (NoteContainer) child.getParent().getModel();
                if (getModel() == oldContainer) {
                    return null;
                }
                Note note = (Note) child.getModel();
                ReparentNoteCommand cmd = new ReparentNoteCommand(getModel(), note);
                cmd.setBounds((Rectangle) constraint);
                cmd.setOldContainer(oldContainer);
                return cmd;
            }
        });
    }

    // ===============================================================
    // GenealogyGraphListener

    /**
     * When a person is added to the model,
     * add a new EditPart to manage the corresponding figure.
     */
    @Override
    public void personAdded(Person p) {
        addChild(createChild(p), 0);
    }

    /**
     * When a person is removed from the model,
     * find and remove the corresponding edit part
     */
    @Override
    public void personRemoved(Person p) {
        genealogyElementRemoved(p);
    }

    /**
     * When a marriage is added to the model,
     * add a new EditPart to manage the corresponding figure.
     */
    @Override
    public void marriageAdded(Marriage m) {
        addChild(createChild(m), 0);
    }

    /**
     * When a marriage is removed from the model,
     * find and remove the corresponding edit part
     */
    @Override
    public void marriageRemoved(Marriage m) {
        genealogyElementRemoved(m);
    }

    /**
     * When a note is added to the model,
     * add a new EditPart to manage the corresponding figure.
     */
    @Override
    public void noteAdded(int index, Note n) {
        addChild(createChild(n), index);
    }

    /**
     * When a note is removed from the model,
     * find and remove the corresponding edit part
     */
    @Override
    public void noteRemoved(Note n) {
        genealogyElementRemoved(n);
    }

    /**
     * When an element is removed from the model,
     * find and remove the corresponding edit part
     */
    private void genealogyElementRemoved(GenealogyElement elem) {
        Object part = getViewer().getEditPartRegistry().get(elem);
        if (part instanceof EditPart) {
            removeChild((EditPart) part);
        }
    }

    @Override
    public void graphCleared() {
    }
}